import threading
import time

import requests
import queue
from  ..db import  mssql
from  ..mq import  mq
import  json
from pymodbus.client.sync import  ModbusSerialClient

sysConfig = {
    "msDB":{
        "host":"meeting.xmgkfw.com" ,
        "port":42049,
        "uid":"sa",
        "pwd":"Hhuc1115" ,
        "db":"zn2024" ,
        "sqlPath":""
    },
    "tdb":{
        "host":"meeting.xmgkfw.com",
        "port":42036 ,
        "token":"Basic cm9vdDpoaHVjMTExNQ==",
        "db":"pcs2023"
    },
    "notify":{
        "rootTopic":"zn2024",
        "host":"hh.hhdata.cn",
        "port":31274,
        "uid":"admin",
        "pwd":"hhuc1115"
    }
}
class TSys:
    def __init__(self):
        self.sysId=0
        self.sysCode = ""
        self.msDB = mssql.TMssqlConnection()
        self.buses ={}
        self.mq = mq.TMq()
    def loadObjs(self , sysObj , busObj , devObj , taskObj , dpObj):
        try:
            for b in busObj :
                bus = TBus()
                bus.moSys = bus
                bus.busId = b['busId']
                bus.busCode = b['busCode']
                bus.busAddr = b['busAddr']
                bus.baudrate = b['baudrate']
                self.buses[bus.busCode] = bus
            for d in devObj :
                dev = TDev()
                dev.bus = self.buses[d["busCode"]]
                dev.devId = d['devId']
                dev.devCode = d['devCode']
                dev.devAddr = d['devAddr']
                dev.devType = d['devType']
                dev.devName = d['devName']
                dev.bus.devs[dev.devCode] = dev
            for t in taskObj :
                task = TTask()
                task.bus = self.buses[t["busCode"]]
                task.taskSer = t["taskSer"]
                task.readCode = t["readCode"]
                task.writeCode = t["writeCode"]
                task.addrFrom = t["addrFrom"]
                task.addrTo = t["addrTo"]
                task.regNum = t["addrNum"]
                task.dev = task.bus.devs[t["devCode"]]
                task.dev.readTasks[task.taskSer] = task
                task.bus.readTasks.append(task)
            for p in dpObj :
                dp = TDatapoints()
                dp.dpId = p['dpId']
                dp.busCode = p["busCode"]
                dp.devCode = p["devCode"]
                dp.taskSer = p["taskSer"]
                dp.addr10 = p["addr10"]
                dp.regAddr = p["regAddr"]
                dp.regBytePos = p["regBytePos"]
                dp.varName = p["varName"]
                dp.varType = p["varType"]
                dp.rate = p["rate"]
                dp.bitFrom = p["bitFrom"]
                dp.bitLen = p["bitLen"]
                dp.unit = p["unit"]
                dp.notifyType = p["notifyType"]
                dp.notifyThreshold = p["notifyThreshold"]
                dp.regIndex = p["regIndex"]
                dp.varTplId = p["varTplId"]
                dp.bus = self.buses[dp.busCode]
                dp.dev = dp.bus.devs[dp.devCode]
                dp.dev.dps[dp.dpId] = dp
                dp.dev.readTasks[dp.taskSer].dps.append(dp)

            print(self)
        except Exception as er:
            print(er)
    def loadObjsFromDB(self):
        try:
            _host = sysConfig["msDB"]["host"]
            _port = sysConfig["msDB"]["port"]
            _uid = sysConfig["msDB"]["uid"]
            _pwd = sysConfig["msDB"]["pwd"]
            _db = sysConfig["msDB"]["db"]
            _sqlPath = sysConfig["msDB"]["sqlPath"]
            self.msDB.setConfig(host=_host,port=_port, uid=_uid, pwd=_pwd , db=_db , sqlPath= _sqlPath)
            sql = "select * from V_SysObjs"
            rs = self.msDB.query(sql , None)
            #print(rs)
            sysJson = rs[0]['znSys']
            busJson = rs[0]['znBus']
            devJson = rs[0]['znDev']
            taskJson = rs[0]['znTask']
            dpJson = rs[0]['znDp']
            objSys = (json.loads(sysJson))[0]
            objBus = json.loads(busJson)
            objDev = json.loads(devJson)
            objTask = json.loads(taskJson)
            objDp = json.loads(dpJson)
            self.loadObjs(objSys , objBus , objDev , objTask , objDp)
        except Exception as er:
            print(er)
    def write2TDB(self , datapointStr , rp):
        try:
            host = sysConfig['notify']['host']
            port = sysConfig['notify']['port']
            token = sysConfig['notify']['token']
            db = sysConfig['notify']['db']
            headers = {
                "Authorization": token,
                'Content-Type': 'application/json',
            }
            influxdb_url = "http://" + host + ":" + str(port) + '/write?db=' + db
            if rp!='':
               influxdb_url = influxdb_url + '&rp=rePolicy_1h'
            resp = requests.post(url=influxdb_url, headers=headers, data=datapointStr)
            print('save to tdb:', resp)
        except Exception as er:
            print(er)
    def notify(self , topic , payload ):
        try:
            if self.mq.isConnected():
                self.mq.sendMsg(topic , payload)
        except Exception as er:
            print(er)
    def openMq(self):
        try:
            self.mq.host = sysConfig['notify']['host']
            self.mq.port = sysConfig['notify']['port']
            self.mq.uid = sysConfig['notify']['uid']
            self.mq.pwd = sysConfig['notify']['pwd']
            self.mq.events['onConnect'] = self.onMqConnected
            self.mq.connect()
            self.mq.client.loop_start()
        except Exception as er:
            print(er)

    def openBuses(self):
        for bus in self.buses.values():
            bus.executeAsync()
    def onTask(self ,  task ):
        print('on task' , task.bus , task.dev , task)
        try:
            tdbStr = task.getChangedDpsTDB()
            notifyJson = task.getChangedDpsNotify()
            self.write2TDB(tdbStr , '')
            topic = "/".join([
                sysConfig["notify"]["rootTopic"],
                "bus",
                task.dev.devCode
            ])
            self.notify(topic , notifyJson)
        except Exception as er :
            print(er)
    def onBusScan(self ,  bus ):
        print('on bus scan' , bus)
        try:
            tdbs =[]
            for dev in bus.devs.values():
                tdbs.append( dev.getDevDpsTDB())
                topic = "/".join([
                    sysConfig["notify"]["rootTopic"] ,
                    "dev",
                    dev.devCode
                ])
                devNotifyJson = dev.getDevDpsNotify()
                self.notify(topic , devNotifyJson)
            tdbsData = "\n".join(tdbs)
            self.write2TDB(tdbsData,'rePolicy_1h')
        except Exception as er :
            print(er)
    def onMqConnected(self ,  client, userdata, flags, rc ):
        try:
            print('on connect to mqtt broker')
        except Exception as er :
            print(er)
class TBus:
    def __init__(self):
        self.moSys = None
        self.busId = 0
        self.busCode = ""
        self.baudrate = 9600
        self.busAddr = 0
        self.readTasks = []
        self.readTaskIndex = -1
        self.writeTaskBuf = queue.Queue(100)
        self.devs = {}

        self.running = False
        self.modbusClient = None
        self.executeThread = None
        self.clinet = None
    def open(self):
        try:
            if self.clinet!=None:
                try:
                    self.clinet.close()
                except Exception as erPort:
                    print(erPort)
            self.clinet = ModbusSerialClient( method="rtu" , port=self.busAddr , baudrate=self.baudrate)
            self.clinet.connect()
            self.running = True
        except Exception as er:
            print(er)
    def close(self):
        try:
            self.running = False
            if self.clinet!=None:
                try:
                    self.clinet.close()
                except Exception as erPort:
                    print(erPort)
            self.clinet = None
        except Exception as er:
            print(er)
    def executeAsync(self):
        try:
            self.executeThread = threading.Thread(target=self.execute , daemon=True)
            self.executeThread.start()
        except Exception as er:
            print(er)
    def execute(self):
        try:
            self.open()
            while self.running:
                try:
                    if self.writeTaskBuf.empty():
                        self.readTaskIndex = self.readTaskIndex + 1
                        if self.readTaskIndex>=len(self.readTasks):
                            self.readTaskIndex = 0
                        readTask = self.readTasks[self.readTaskIndex]
                        if readTask.readCode == 3 :
                            regResult = self.clinet.read_holding_registers(address=readTask.addrFrom, count=readTask.regNum, unit=readTask.dev.devAddr)
                            if regResult.isError():
                                print('read register values faild' , readTask)
                            else:
                                for dp in readTask.dps:
                                    dp.setValue(regResult)
                        elif readTask.readCode == 4 :
                            regResult = self.clinet.read_input_registers(address=readTask.addrFrom, count=readTask.regNum, unit=readTask.dev.devAddr)
                            if regResult.isError():
                                print('read register values faild' , readTask)
                            else:
                                for dp in readTask.dps:
                                    dp.setValue(regResult)
                        self.moSys.onTask( self , readTask)
                        if self.readTaskIndex+1>= len( self.readTasks):
                            self.moSys.onBusScan(self)
                    else:
                        writeTask = self.writeTaskBuf.get()
                        print('execute write.', writeTask)
                except Exception as err:
                    print(err)
            self.close()
        except Exception as er:
            print(er)
class TDev:
    def __init__(self):
        self.devId = 0
        self.devCode = ''
        self.bus={}
        self.devName = ""
        self.devType = ""
        self.devAddr = 0
        self.readTasks = {}
        self.dps ={}
    def getDevDpsTDB(self):
        res = ""
        try:
            tabName = "zn2024"+self.devCode
            tags = [
                "bus="+self.busCode,
                "dev="+self.devCode
            ]
            fields =[]
            for dp in self.dps.values():
                fields.append('v'+str(dp.dpId)+'='+str(dp.value))
            res = tabName +","
            res = res + ",".join(tags)+" "
            res = res + ",".join(fields)
        except Exception as er:
            print(er)
        return  res
    def getDevDpsNotify(self):
        res = {
            "flag":"zn2024",
            "reason":"bus",
            "bus": self.bus.busCode ,
            "devs":[]
        }
        try:
            for dev in self.devs.values():
                devInfo = {
                    "devCode": dev.devCode ,
                    "dps":[]
                }
                for dp in dev.dps.values():
                    devInfo['dps'].append({
                        "dpId": dp.dpId ,
                        "varTplId":dp.varTplId,
                        "dpName": dp.varName ,
                        "value":dp.value,
                        "oldValue":dp.oldValue,
                        "updateTime": dp.updateTime
                    })
                res["devs"].append(devInfo)
        except Exception as er:
            print(er)
        return  json.dump(res)
class TTask:
    def __init__(self):
        self.taskSer = 0
        self.bus = None
        self.dev = None
        self.readCode =0
        self.writeCode = 0
        self.addrFrom = 0
        self.addrTo =0
        self.regNum = 0
        self.writeValues =[]
        self.dps = []
    def getChangedDpsTDB(self):
        res = ""
        try:
            dps=[]
            for dp in self.dps:
                item = dp.getChangedTDB()
                dps.append(item)
            res = "\n".join(dps)
        except Exception as er:
            print(er)
        return  res
    def getChangedDpsNotify(self):
        res = {
            "flag":"zn2024",
            "reason":"change",
            "bus": self.bus.busCode ,
            "dev": self.dev.devCode ,
            "ser": self.taskSer ,
            "dps":{}
        }
        try:
            for dp in self.dps:
                res["dps"]["v"+str(dp.dpId)] = dp.value
        except Exception as er:
            print(er)
        return  json.dump(res)
class TDatapoints:
    def __init__(self):
        self.dpId = 0
        self.readTask = None
        self.bus = None
        self.dev = None
        self.busCode=""
        self.devCode=""
        self.taskSer=0
        self.addr10=0
        self.regAddr=""
        self.regBytePos=0
        self.varName=""
        self.varType=""
        self.rate=0
        self.bitFrom=0
        self.bitLen=0
        self.unit=""
        self.notifyType=""
        self.notifyThreshold=0
        self.regIndex = 0
        self.varTplId = 0

        self.updateTime =0
        self.value = 0
        self.oldValue = 0
    def setValue(self, regs):
        try:
            self.oldValue = self.value
            if self.varType == 'V01':
                regValue = regs.getRegister(self.regIndex)
                val = (regValue >> (16-self.bitFrom-1))&1
                self.value = val
            elif self.varType == 'V02':
                regValue = regs.getRegister(self.regIndex)
                val = (regValue >> (16-self.bitFrom-self.bitLen))&(pow(2,self.bitLen) -1)
                self.value = val
            elif self.varType == 'V03':
                regValue = regs.getRegister(self.regIndex)
                bytes = int.to_bytes(regValue,2,byteorder='big', signed=False)
                if bytes[0]>0x80:
                    regValue = int.from_bytes(bytes,byteorder='big', signed=True)
                    bytes=None
                self.value = regValue
            elif self.varType == 'V04':
                self.value = regs.getRegister(self.regIndex)
            elif self.varType == 'V05':
                regValue0 = regs.getRegister(self.regIndex)
                regValue1 = regs.getRegister(self.regIndex+1)
                bytes0 = int.to_bytes(regValue0,2,byteorder='big', signed=False)
                bytes1 = int.to_bytes(regValue1,2,byteorder='big', signed=False)
                bytes = bytearray([bytes0[0],bytes0[1],bytes1[0],bytes1[1]])
                regValue = int.from_bytes(bytes , byteorder='big', signed=False)
                self.value = regValue
            elif self.varType == 'V06':
                regValue = regs.getRegister(self.regIndex)
                bytes = int.to_bytes(regValue,2,byteorder='big', signed=False)
                self.value = bytes[0]
            elif self.varType == 'V07':
                regValue = regs.getRegister(self.regIndex)
                bytes = int.to_bytes(regValue,2,byteorder='big', signed=False)
                self.value = bytes[1]
            elif self.varType == 'V08':
                regValue = regs.getRegister(self.regIndex)
                if regValue :
                    self.value = 1
                else:
                    self.value = 0
            elif self.varType == 'V09':
                regValue0 = regs.getRegister(self.regIndex)
                regValue1 = regs.getRegister(self.regIndex+1)
                bytes0 = int.to_bytes(regValue0,2,byteorder='big', signed=False)
                bytes1 = int.to_bytes(regValue1,2,byteorder='big', signed=False)
                bytes = bytearray([bytes0[0],bytes0[1],bytes1[0],bytes1[1]])
                regValue = int.from_bytes(bytes , byteorder='big', signed=True)
                self.value = regValue
            self.updateTime = time.time()
        except Exception as er:
            print(er)
    def isChanged(self):
        res =False
        try:
            pass
        except Exception as er:
            print(er)
        return  res
    def getChangedTDB(self):
        res =""
        try:
            res = "zn20204changed" +","
            tags =[
                "bus="+ self.bus.busCode ,
                "dev="+self.dev.devCode ,
                "ser="+str(self.taskSer) ,
                "dpId="+str(self.dpId) ,
                "varTplId="+str(self.dpId)
            ]
            fields =[
                "v="+str(self.value) ,
                "ov="+str(self.oldValue)
            ]
            res = res + ",".join(tags)+" "+",".join(fields)
        except Exception as er:
            print(er)
        return  res

modSys = TSys()
modSys.loadObjsFromDB()
modSys.openMq()
modSys.openBuses()

print('load zn modbus rtu module.')